package lockmanager.quickcheck;



import static net.java.quickcheck.generator.PrimitiveGenerators.integers;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import lockmanager.ResourcePool;
import lockmanager.ResourcePoolBlocking;
import lockmanager.ResourcePoolNonBlocking;

public class State {
	
	private ResourcePool pool = null;
	private List<Object> resourceFree = null;
	private List<Object> resourceUse = null;
	private ExecutorService threadPool;
	
	private boolean start = false;
	private Object startSignal = new Object();
	private Object readySignal = new Object();
	private int readyThread = 0;
	private AtomicInteger logId = new AtomicInteger(0);
	private Function[] functions = new Function[3];
	private int[] rev = new int[3];

	
	
	enum Function{
		GET,PUT
	}
	
	State(){
		resourceFree = new ArrayList<Object>();
		resourceUse = new ArrayList<Object>();
		
	}
	
	public void initState(){
		pool = new ResourcePoolNonBlocking();
		//pool = new ResourcePoolBlocking();
		for(int i=0;i<10;i++){
			resourceFree.add(i,pool.getResouceList().get(i));
		}
	}
	
	//because the thread are running concurrently, so we can't rely on the
	//the order of the array, I use a strategy that first put all the item,
	//then handle all the get.
	public void precondition(){
		for(int i=0;i<3;i++){
			if(functions[i].equals(Function.PUT))
				resourceFree.add(rev[i]);
		}
		
		for(int i=0;i<3;i++){
			if (functions[i].equals(Function.GET)) {
				if (rev[i] != -100) {
					resourceFree.remove((Object) rev[i]);
					resourceUse.add(rev[i]);
				}
			}
		}
		
		
	}
	
	public boolean postcondition(){
		
		boolean condition = true;
		
		//all the free item should be available in the resource pool.
		for(Object o : resourceFree){
			if(!pool.getResouceList().contains(o))
				condition = false;
		}
		
		//all the ocupied item shouldn't be in the resource pool.
		for(Object o : resourceUse){
			if(pool.getResouceList().contains(o))
				condition = false;
		}
		
		//If the Get return nothing, that means the resource pool should be
		//empty, which the resource in use should be full,and the resourceFree
		//should be the size of 0.
		for (int i = 0; i < 3; i++) {
			if (functions[i].equals(Function.GET)) {
				if (rev[i] == -100) {
					if (resourceFree.size() != 0 || resourceUse.size() != 10)
						condition = false;
				}
			}
		}
			
		showLog(condition);
		
		return condition;
	}
	

	private void randomSelect(int i){
		
		if(i == 0){
			functions[0] = Function.GET;
			functions[1] = Function.GET;
			functions[2] = Function.GET;
		}else if(i == 1){
			functions[0] = Function.PUT;
			functions[1] = Function.GET;
			functions[2] = Function.GET;
		}else if(i == 2){
			functions[0] = Function.PUT;
			functions[1] = Function.PUT;
			functions[2] = Function.GET;
		}else if(i == 3){
			functions[0] = Function.PUT;
			functions[1] = Function.PUT;
			functions[2] = Function.PUT;
		}
	}
	
	public void command(){
		
		if(resourceUse.size()>=3){
			randomSelect(integers(0,3).next());
		}
		else if(resourceUse.size() == 2){
			randomSelect(integers(0,2).next());
		}else if(resourceUse.size() == 1){
			randomSelect(integers(0,1).next());
		}else if(resourceUse.size() == 0){
			randomSelect(0);
		}
		
	}

	public void nextState(){
		
		command();
		threadPool = Executors.newFixedThreadPool(4);
		for(int i=0;i<3;i++)
			rev[i] = -100;
		start = false;
		readyThread = 0;
		
		for (int i = 1; i <= 3; i++) {
			if (functions[i-1].equals(Function.PUT))
				startPUT(i);
			else
				startGET(i);
		}
		
		//waiting for all the threads are ready.
		while(true){
			synchronized(readySignal){
				if(readyThread == 3)
					break;
				else{
					try {
						readySignal.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
		
		synchronized(startSignal){
			start = true;
			startSignal.notifyAll();
		}
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		/**
		 * shutdown thread pool
		 */
		threadPool.shutdown();
		
		threadPool.shutdownNow();
		
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public void showLog(boolean condition){
		
		logId.incrementAndGet();
		System.out.println("==========> Test Case "+logId.get()+"<=========");
		for(int i=0;i<3;i++){
			System.out.println("Function"+(i+1)+" : "+functions[i]);
			System.out.println("Return Value"+(i+1)+" : "+rev[i]);
		}
		System.out.println("Resource Pool");
		for(Object o : pool.getResouceList()){
			System.out.print(o+"  ");
		}
		System.out.println("");
		System.out.println("Resource Free");
		for(Object o : resourceFree){
			System.out.print(o+"  ");
		}
		System.out.println("");
		System.out.println("Resource Use");
		for(Object o : resourceUse){
			System.out.print(o+"  ");
		}
		System.out.println("");
		System.out.println("Post Condition "+ condition);
		
		System.out.println("==========>End<=========");
	}
	
	public void startPUT(int id){
		
		int value = (Integer) resourceUse.remove(0);
		TestThread th = new TestThread(id,Function.PUT,value);
		threadPool.execute(th);
	}
	
	public void startGET(int id){
		TestThread th = new TestThread(id,Function.GET,-1);
		threadPool.execute(th);
	}
	
	private class TestThread implements Runnable{
		
		private Function f;
		private int id;
		private int put;
		
		TestThread(int id,Function f,int put){
			this.id = id;
			this.f = f;
			this.put = put;
		}

		@Override
		public void run() {
			
			while(!start){
				
				synchronized(startSignal){
					
					try {
						synchronized(readySignal){
							readyThread++;
							readySignal.notifyAll();
						}
						startSignal.wait();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
			
			//System.out.println("Thread id: "+id+" Function : "+f+" value : "+ put);
			
			switch(f){
			case PUT:
				pool.put(put);
				setReturnValue(id,put);
				break;
			case GET:
				Object o = pool.get();
				if (o != null) {
					int value = (Integer)o;
					setReturnValue(id, value);
				}
				break;
			}
			
			//System.out.println("Thread id: "+id+" Function : "+f+" value : "+ put);
		}
		
		void setReturnValue(int id, int value){
			
			switch(id){
			case 1:
				rev[0] = value;
				break;
			case 2:
				rev[1] = value;
				break;
			case 3:
				rev[2] = value;
				break;
			}
		}
		
	}

}
